本來沒有想要做角色升級的機制,但看到圖片素材中角色有分成不同裝備,就覺得應該很有趣,可以來做做看。所以這篇就是要把專案中的角色成長系統搭建起來。
預設玩家角色會從 Lv0 開始,透過擊敗敵人累積經驗值,升級時自動更新基礎攻、防以及角色圖案,而 HUD 也能即時顯示等級進度。
預計達成的目標:
四個等級角色素材
新增的 new_demo/src/components/progression.rs
定義 PlayerProgression
component,專門儲存玩家等級與累積經驗,並提供查詢基礎數值的 helper:
#[derive(Component, Debug, Clone)]
pub struct PlayerProgression {
pub level: usize,
pub experience: u32,
}
impl PlayerProgression {
pub fn new() -> Self { Self { level: 0, experience: 0 } }
pub fn next_level_requirement(&self) -> Option<u32> {
PLAYER_LEVEL_XP_REQUIREMENTS.get(self.level).copied()
}
pub fn base_attack(&self) -> i32 {
PLAYER_LEVEL_BASE_ATTACK
.get(self.level)
.copied()
.unwrap_or_else(|| *PLAYER_LEVEL_BASE_ATTACK.last().unwrap())
}
pub fn sprite_path(&self) -> &'static str {
PLAYER_LEVEL_SPRITE_PATHS
.get(self.level)
.copied()
.unwrap_or_else(|| *PLAYER_LEVEL_SPRITE_PATHS.last().unwrap())
}
}
相關常數集中在 new_demo/src/constants.rs
:
pub const PLAYER_MAX_LEVEL: usize = 3;
pub const PLAYER_LEVEL_XP_REQUIREMENTS: [u32; PLAYER_MAX_LEVEL] = [120, 240, 420];
pub const PLAYER_LEVEL_BASE_ATTACK: [i32; PLAYER_MAX_LEVEL + 1] = [15, 24, 34, 46];
pub const PLAYER_LEVEL_BASE_DEFENSE: [i32; PLAYER_MAX_LEVEL + 1] = [4, 7, 11, 16];
pub const PLAYER_LEVEL_SPRITE_PATHS: [&str; PLAYER_MAX_LEVEL + 1] = [
"characters/players/knight_lv0.png",
"characters/players/knight_lv1.png",
"characters/players/knight_lv2.png",
"characters/players/knight_lv3.png",
];
這樣之後調整等級曲線時,只需要修改常數就可以同步影響所有系統。
new_demo/src/systems/setup.rs
把 progression 掛在玩家身上,一出場時就依照 Lv0 的設定初始化攻、防與角色圖案:
let progression = PlayerProgression::new();
commands
.spawn((
Player,
Sprite::from_image(asset_server.load(progression.sprite_path())),
Health::new(PLAYER_INITIAL_HEALTH),
Attack::new(progression.base_attack()),
Defense::new(progression.base_defense()),
Stamina::new(PLAYER_MAX_STAMINA, PLAYER_STAMINA_REGEN_PER_SECOND),
EquippedWeapon::new(WeaponKind::Level1),
progression,
));
這代表角色會以 knight_lv0.png
亮相,之後升級時則由 progression 決定下一張圖。
在敵人淡出前的 despawn_dead_enemies_system
中,新增 EnemyDefeatedEvent
記錄來源與獲得的經驗值 (new_demo/src/systems/enemy.rs
):
if experience_reward > 0 {
defeated_events.write(EnemyDefeatedEvent {
experience: experience_reward,
enemy_name: enemy_label,
});
info!("擊敗{}獲得 {} EXP", enemy_label, experience_reward);
}
目前預設:史萊姆 30、獨眼巨人 90、寶箱怪 110。後續要調整節奏時,直接改 constants.rs
即可。
為集中成長邏輯,建立 ProgressionPlugin
(new_demo/src/plugins/progression.rs
) 並在 main.rs
內掛上:
App::new()
.add_plugins((
WorldPlugin,
PlayerPlugin,
UiPlugin,
EnemyPlugin,
ProgressionPlugin,
// ...
))
插件註冊兩個核心系統 (new_demo/src/systems/progression.rs
):
apply_enemy_experience_rewards
:累加經驗值、處理可能跨越多級的升級,並發送 PlayerLevelUpEvent
。apply_player_level_up_effects
:收到升級事件後更新 Attack
/Defense
基礎值與角色 sprite。attack.base = progression.base_attack();
defense.base = progression.base_defense();
sprite.image = asset_server.load(progression.sprite_path());
info!(
"玩家等級提升至 Lv.{}!基礎攻擊 {},基礎防禦 {}",
level, attack.base, defense.base,
);
系統排程在敵人淡出之後執行,確保擊倒敵人後 HUD 與 Console 立即反映最新狀態。如果玩家已達 Lv3,則會在 log 中提示經驗值不再累積。
為了讓成長資訊更直觀,update_player_stats_panel
(new_demo/src/systems/player_stats.rs
) 新增等級與經驗行,並調整面板高度:
let level_line = if let Some(requirement) = progression.next_level_requirement() {
format!(
"LV {:>2} EXP {:>4}/{:>4}",
progression.level,
progression.experience,
requirement,
)
} else {
format!("LV {:>2} EXP MAX", progression.level)
};
畫面左上角除了原本的攻防/耐力資訊,現在還能看到等級進度條數字。配合 Console log,可以很清楚地確認升級節奏是否符合預期。
ProgressionPlugin
負責,後續擴充更容易。接下來考慮加上的功能:
今日程式碼同步至 repo